package com.adrninistrator.jacg.dboper;

import com.adrninistrator.jacg.common.DC;
import com.adrninistrator.jacg.common.JACGConstants;
import com.adrninistrator.jacg.common.enums.DbInsertMode;
import com.adrninistrator.jacg.common.enums.DbTableInfoEnum;
import com.adrninistrator.jacg.common.enums.SqlKeyEnum;
import com.adrninistrator.jacg.util.JACGSqlUtil;
import com.adrninistrator.javacg.common.enums.JavaCGYesNoEnum;
import com.adrninistrator.javacg.exceptions.JavaCGRuntimeException;
import com.adrninistrator.javacg.util.JavaCGClassMethodUtil;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * @author adrninistrator
 * @date 2022/8/23
 * @description: 数据库公共操作封装对象
 */
public class DbOperWrapper {
    private static final Logger logger = LoggerFactory.getLogger(DbOperWrapper.class);

    private static final AtomicInteger ATOMIC_INTEGER = new AtomicInteger(0);

    // 预编译SQL语句缓存，不能使用静态字段，否则多个任务之间会相互影响
    private final Map<String, String> sqlCacheMap = new ConcurrentHashMap<>();

    /*
        类名相同但包名不同的类名Map
        key     表名后缀
        value   类名相同但包名不同的类名Set
     */
    private final Map<String, Set<String>> duplicateSimpleClassNameMap = new HashMap<>();

    private final DbOperator dbOperator;

    private final String appName;

    private final String tableSuffix;

    private final String objSeq;

    private final int dbInsertBatchSize;

    // 类名相同但包名不同的类名Set
    private Set<String> duplicateSimpleClassNameSet = null;

    DbOperWrapper(DbOperator dbOperator) {
        this.dbOperator = dbOperator;
        appName = dbOperator.getAppName();
        tableSuffix = dbOperator.getTableSuffix();

        objSeq = "dbwo@" + ATOMIC_INTEGER.incrementAndGet();
        dbInsertBatchSize = dbOperator.getDbInsertBatchSize();
        logger.info("objSeq [{}]", objSeq);
    }

    private String genSqlKey(String sqlKey, int num) {
        if (num == 0) {
            return sqlKey;
        }
        return sqlKey + JACGConstants.FLAG_AT + num;
    }

    private String getCachedSql(String sqlKey, int num) {
        return sqlCacheMap.get(genSqlKey(sqlKey, num));
    }

    /**
     * 获取缓存的sql语句，参数数量可变
     *
     * @param sqlKeyEnum
     * @param num
     * @return
     */
    public String getCachedSql(SqlKeyEnum sqlKeyEnum, int num) {
        return getCachedSql(String.valueOf(sqlKeyEnum.ordinal()), num);
    }

    public String getCachedSql(String sqlKey) {
        return getCachedSql(sqlKey, 0);
    }

    /**
     * 获取缓存的sql语句，参数数量固定
     *
     * @param sqlKeyEnum
     * @return
     */
    public String getCachedSql(SqlKeyEnum sqlKeyEnum) {
        return getCachedSql(String.valueOf(sqlKeyEnum.ordinal()));
    }

    private String cacheSql(String sqlKey, String sql, String sqlKey4Print, int num) {
        // 根据sql语句的key与参数数量，生成最终的key
        String finalSqlKey = genSqlKey(sqlKey, num);

        // 替换sql语句中的appName
        String finalSql = JACGSqlUtil.replaceFlagInSql(sql, appName, tableSuffix);
        if (sqlCacheMap.putIfAbsent(finalSqlKey, finalSql) == null) {
            // 假如有指定用于在日志中打印的key，则在日志中打印出来
            logger.info("[{}] cache sql: [{} {}] [{}]", objSeq, finalSqlKey, sqlKey4Print, finalSql);
        }
        return finalSql;
    }

    /**
     * 缓存并格式化sql，参数数量可变
     *
     * @param sqlKeyEnum
     * @param sql
     * @param num
     */
    public String cacheSql(SqlKeyEnum sqlKeyEnum, String sql, int num) {
        return cacheSql(String.valueOf(sqlKeyEnum.ordinal()), sql, sqlKeyEnum.name(), num);
    }

    public String cacheSql(String sqlKey, String sql, String key4Print) {
        return cacheSql(sqlKey, sql, key4Print, 0);
    }

    /**
     * 缓存并格式化sql，参数数量固定
     *
     * @param sqlKeyEnum
     * @param sql
     */
    public String cacheSql(SqlKeyEnum sqlKeyEnum, String sql) {
        return cacheSql(String.valueOf(sqlKeyEnum.ordinal()), sql, sqlKeyEnum.name());
    }

    /**
     * 格式化sql语句，适用于不需要缓存的sql语句
     *
     * @param sql 格式化前的sql语句
     * @return 格式化后的sql语句
     */
    public String formatSql(String sql) {
        // 替换sql语句中的appName
        String finalSql = JACGSqlUtil.replaceFlagInSql(sql, appName, tableSuffix);
        logger.info("[{}] format sql: [{}]", objSeq, finalSql);
        return finalSql;
    }

    /**
     * 生成用于插入数据库的sql语句并缓存
     *
     * @param dbTableInfoEnum
     * @param dbInsertMode
     * @return
     */
    public String genAndCacheInsertSql(DbTableInfoEnum dbTableInfoEnum, DbInsertMode dbInsertMode) {
        String key = dbTableInfoEnum.getInsertSqlKey();
        String sql = getCachedSql(key);
        if (sql == null) {
            String[] columns = dbTableInfoEnum.getColumns();
            sql = dbInsertMode.getMode() + dbTableInfoEnum.getTableName() + JACGSqlUtil.genColumnString(columns) + " values " + JACGSqlUtil.genQuestionString(columns.length);
            sql = cacheSql(key, sql, dbTableInfoEnum.getTableNameKeyword());
        }
        return sql;
    }

    /**
     * 查找类名相同但包名不同的类，使用ThreadLocal中的表名后缀
     *
     * @return
     */
    public boolean findDuplicateClass() {
        return findDuplicateClass(dbOperator.getTableSuffix());
    }

    /**
     * 查找类名相同但包名不同的类，使用指定的表名后缀
     *
     * @param tableSuffix
     * @return
     */
    public boolean findDuplicateClass(String tableSuffix) {
        logger.info("查找类名相同但包名不同的类 {}", StringUtils.defaultString(tableSuffix, ""));
        Set<String> usedDuplicateSimpleClassNameSet;

        if (StringUtils.isBlank(tableSuffix)) {
            // 表名后缀为null，使用固定的Set对象
            duplicateSimpleClassNameSet = new HashSet<>();
            usedDuplicateSimpleClassNameSet = duplicateSimpleClassNameSet;
        } else {
            // 表名后缀为null，使用Map中的Set对象
            usedDuplicateSimpleClassNameSet = duplicateSimpleClassNameMap.computeIfAbsent(tableSuffix, k -> new HashSet<>());
        }

        // 以下sql语句不能缓存，因为可能被不同的表名后缀使用
        // 查找类名与唯一类名相同，且唯一类名中包含.的唯一类名
        String sql = "select " + DC.CN_SIMPLE_CLASS_NAME +
                " from " + DbTableInfoEnum.DTIE_CLASS_NAME.getTableName(appName, tableSuffix) +
                " where " + DC.CN_DUPLICATE_CLASS + " = ?";
        String finalSql = formatSql(sql);
        List<String> list = dbOperator.queryListOneColumn(finalSql, String.class, JavaCGYesNoEnum.YES.getIntValue());
        if (list == null) {
            return false;
        }
        if (list.isEmpty()) {
            return true;
        }

        for (String simpleClassName : list) {
            String duplicateSimpleClassName = JavaCGClassMethodUtil.getSimpleClassNameFromFull(simpleClassName);
            usedDuplicateSimpleClassNameSet.add(duplicateSimpleClassName);
        }
        logger.info("找到类名相同但包名不同的类 {}", StringUtils.join(usedDuplicateSimpleClassNameSet, " "));
        return true;
    }

    /**
     * 将类名表中的同名类更新为使用完整类名之前，查找类名相同但包名不同的类
     *
     * @return
     */
    public Set<String> findDuplicateClassBeforeUpdate() {
        SqlKeyEnum sqlKeyEnum = SqlKeyEnum.CN_QUERY_DUPLICATE_CLASS_BEFORE_UPDATE;
        String sql = getCachedSql(sqlKeyEnum);
        if (sql == null) {
            sql = "select " + DC.CN_SIMPLE_CLASS_NAME +
                    " from " + DbTableInfoEnum.DTIE_CLASS_NAME.getTableName() +
                    " group by " + DC.CN_SIMPLE_CLASS_NAME +
                    " having count(" + DC.CN_SIMPLE_CLASS_NAME + ") > 1";
            sql = cacheSql(sqlKeyEnum, sql);
        }

        List<String> list = dbOperator.queryListOneColumn(sql, String.class);
        if (list == null) {
            return Collections.emptySet();
        }
        return new HashSet<>(list);
    }

    /**
     * 将类名表中的同名类更新为使用完整类名，并记录同名类
     *
     * @return
     */
    public boolean updateAllSimpleName2Full() {
        Set<String> foundDuplicateSimpleClassNameSet = findDuplicateClassBeforeUpdate();
        if (foundDuplicateSimpleClassNameSet.isEmpty()) {
            logger.info("不存在类名相同但包名不同的类");
            return true;
        }

        logger.info("找到类名相同但包名不同的类 {}", StringUtils.join(foundDuplicateSimpleClassNameSet, " "));
        SqlKeyEnum sqlKeyEnum = SqlKeyEnum.CN_UPDATE_SIMPLE_2_FULL;
        String sql = getCachedSql(sqlKeyEnum);
        if (sql == null) {
            sql = "update " + DbTableInfoEnum.DTIE_CLASS_NAME.getTableName() +
                    " set " + DC.CN_SIMPLE_CLASS_NAME + " = " + DC.CN_CLASS_NAME + "," + DC.CN_DUPLICATE_CLASS + " = ?" +
                    " where " + DC.CN_SIMPLE_CLASS_NAME + " = ?";
            sql = cacheSql(sqlKeyEnum, sql);
        }

        for (String duplicateClassName : foundDuplicateSimpleClassNameSet) {
            // 将class_name_表的simple_name更新为full_name
            if (dbOperator.update(sql, JavaCGYesNoEnum.YES.getIntValue(), duplicateClassName) == null) {
                return false;
            }
        }

        duplicateSimpleClassNameSet = foundDuplicateSimpleClassNameSet;
        return true;
    }

    /**
     * 根据完整类名获取对应的类名，使用ThreadLocal中的表名后缀
     *
     * @param className
     * @return
     */
    public String getSimpleClassName(String className) {
        return getSimpleClassName(className, dbOperator.getTableSuffix());
    }

    /**
     * 根据完整类名获取对应的类名，使用指定的表名后缀
     * 若当前简单类名存在1个以上，则返回完整类名
     * 若当前简单类名只有1个，则返回简单类名
     * 假如当前数据库中不存在对应的类，也会返回非空的类名
     *
     * @param className   完整类名信息
     * @param tableSuffix 表名后缀
     * @return 完整类名或简单类名
     */
    public String getSimpleClassName(String className, String tableSuffix) {
        Set<String> usedDuplicateSimpleClassNameSet = null;
        // 根据表名后缀选择当前使用的类名相同但包名不同的类名Set
        if (StringUtils.isBlank(tableSuffix)) {
            if (duplicateSimpleClassNameSet != null) {
                usedDuplicateSimpleClassNameSet = duplicateSimpleClassNameSet;
            }
        } else {
            if (duplicateSimpleClassNameMap.containsKey(tableSuffix)) {
                usedDuplicateSimpleClassNameSet = duplicateSimpleClassNameMap.get(tableSuffix);
            }
        }
        if (usedDuplicateSimpleClassNameSet == null) {
            // 查找类名相同但包名不同的类
            if (!findDuplicateClass(tableSuffix)) {
                throw new JavaCGRuntimeException("查询同名类失败");
            }
            if (StringUtils.isBlank(tableSuffix)) {
                usedDuplicateSimpleClassNameSet = duplicateSimpleClassNameSet;
            } else {
                usedDuplicateSimpleClassNameSet = duplicateSimpleClassNameMap.get(tableSuffix);
            }
        }

        String simpleClassName = JavaCGClassMethodUtil.getSimpleClassNameFromFull(className);
        if (usedDuplicateSimpleClassNameSet.contains(simpleClassName)) {
            return className;
        }
        return simpleClassName;
    }

    //
    public DbOperator getDbOperator() {
        return dbOperator;
    }

    public int getDbInsertBatchSize() {
        return dbInsertBatchSize;
    }
}
